#include "LayerStackupModel.h"
#include "ItemModel.h"
#include "Document.h"

#include "LeIpc2581/Stackup.h"
#include "LeIpc2581/StackupGroup.h"
#include "LeIpc2581/StackupLayer.h"

#include <QDebug>
#include <QSet>

class LayerStackupItem
{
public:
    explicit LayerStackupItem(const Ipc2581b::Stackup *stackup):
        m_rootData(stackup), m_groupData(nullptr), m_layerData(nullptr),
        m_parent(nullptr)
    {
    }

    explicit LayerStackupItem(const Ipc2581b::StackupGroup *group):
        m_rootData(nullptr), m_groupData(group), m_layerData(nullptr),
        m_parent(nullptr)
    {
    }

    explicit LayerStackupItem(const Ipc2581b::StackupLayer *layer):
        m_rootData(nullptr), m_groupData(nullptr), m_layerData(layer),
        m_parent(nullptr)
    {
    }

    virtual ~LayerStackupItem()
    {
        qDeleteAll(m_children);
    }

    inline void addChild(LayerStackupItem *item)
    {
        item->m_parent = this;
        m_children.append(item);
    }

    inline LayerStackupItem *child(int row) const
    {
        if (row <0 || row >= m_children.count())
            return nullptr;
        return m_children.value(row);
    }

    inline int childCount() const
    {
        return m_children.count();
    }

    inline int row() const
    {
        if (m_parent != nullptr)
            return m_parent->m_children.indexOf(const_cast<LayerStackupItem*>(this));
        return 0;
    }

    inline LayerStackupItem *parent() const
    {
        return m_parent;
    }

    QVariant name() const
    {
        if (m_rootData != nullptr)
            return m_rootData->nameOptional.value;
        if (m_groupData != nullptr)
            return m_groupData->name;
        if (m_layerData != nullptr)
            return m_layerData->layerOrGroupRef;
        return QVariant();
    }

    QVariant thickness() const
    {
        qreal value;
        if (m_rootData != nullptr)
            value = m_rootData->overallThickness;
        if (m_groupData != nullptr)
            value = m_groupData->thickness;
        if (m_layerData != nullptr)
            value = m_layerData->thickness;
        return QString::number(value);
    }

    QVariant comment() const
    {
        if (m_rootData != nullptr)
            return m_rootData->commentOptional.value;
        if (m_groupData != nullptr)
            return m_groupData->commentOptional.value;
        if (m_layerData != nullptr)
            return m_layerData->commentOptional.value;
        return QVariant();
    }

    QVariant bomItemRef() const
    {
        if (m_rootData != nullptr && m_rootData->matDesOptional.hasValue)
            return m_rootData->matDesOptional.value->name;
        else if (m_groupData != nullptr && m_groupData->matDesOptional.hasValue)
            return m_groupData->matDesOptional.value->name;
        if (m_layerData != nullptr && m_layerData->matDesOptional.hasValue)
            return m_layerData->matDesOptional.value->name;
        return QVariant();
    }

    QVariant specificationKeys() const
    {
        QList<Ipc2581b::IdRef*> refList;
        if (m_rootData != nullptr)
            refList =  m_rootData->specRefList;
        else if (m_groupData != nullptr)
            refList = m_groupData->specRefList;
        else if (m_layerData != nullptr)
            refList =  m_layerData->specRefList;
        else
            return QVariant();
        QStringList idList;
        for (const Ipc2581b::IdRef *ref: refList)
            idList.append(ref->id);
        if (idList.isEmpty())
            return QVariant();
        return idList;
    }

    // FIXME: SpecRef list
    // FIXME: sequence for StackupLayer
private:

    const Ipc2581b::Stackup *m_rootData;
    const Ipc2581b::StackupGroup *m_groupData;
    const Ipc2581b::StackupLayer *m_layerData;
    LayerStackupItem* m_parent;
    QList<LayerStackupItem*> m_children;
};


class LayerStackupModelPrivate
{
    Q_DISABLE_COPY(LayerStackupModelPrivate)
    Q_DECLARE_PUBLIC(LayerStackupModel)
    LayerStackupModel * const q_ptr;

public:
    explicit LayerStackupModelPrivate(LayerStackupModel *model):
        q_ptr(model)
    {

    }

    ~LayerStackupModelPrivate()
    {
        delete m_root;
    }

    const Document *m_document = nullptr;
    const Ipc2581b::Stackup *m_stackup = nullptr;
    LayerStackupItem *m_root = nullptr;
};

LayerStackupModel::LayerStackupModel(QObject *parent):
    QAbstractItemModel(parent),
    d_ptr(new LayerStackupModelPrivate(this))
{
}

LayerStackupModel::~LayerStackupModel()
{

}

void LayerStackupModel::setIpcStackup(const Document *document, const Ipc2581b::Stackup *stackup)
{
    Q_D(LayerStackupModel);

    beginResetModel();

    d->m_document = document;
    d->m_stackup = stackup;

    if (!d->m_stackup->stackupGroupList.isEmpty())
    {
        // First read all the groups
        QMap<QString, LayerStackupItem*> groupMap;
        for (auto group: d->m_stackup->stackupGroupList)
        {
            auto groupItem = new LayerStackupItem(group);
            if (groupMap.contains(group->name))
            {
                qWarning() << QString("The stackup '%1' has already a group named '%2'")
                              .arg(d->m_stackup->nameOptional.value)
                              .arg(group->name);
                continue;
            }
            groupMap.insert(group->name, groupItem);
        }

        // Then for each group, add it's sug-groups and layers,
        // building up the tree
        for (auto group: d->m_stackup->stackupGroupList)
        {
            auto parentItem = groupMap.value(group->name);
            for (auto layer: group->stackupLayerList)
            {
                const QString ref = layer->layerOrGroupRef;
                if (groupMap.contains(ref))
                {
                    auto childItem = groupMap.value(ref);
                    parentItem->addChild(childItem);
                }
                else
                {
                    auto childItem = new LayerStackupItem(layer);
                    parentItem->addChild(childItem);
                }
            }
        }

        // Finally, there should be only one parentless group left:
        // the root group of the layer stack
        bool rootFound = false;
        for (LayerStackupItem* item: groupMap.values())
        {
            if (!rootFound && item->parent() == nullptr)
            {
                rootFound = true;
                d->m_root = new LayerStackupItem(d->m_stackup);
                d->m_root->addChild(item);
            }
            else if (rootFound && item->parent() == nullptr)
            {
                qWarning() << QString("Stackup '%1' has more than 1 root group")
                              .arg(d->m_stackup->nameOptional.value);
            }
        }
        if (!rootFound)
        {
            qWarning() << QString("Stackup '%1' has no root group?!?")
                          .arg(d->m_stackup->nameOptional.value);
        }
    }
    else
    {
        qWarning() << QString("Stackup '%1' has no root group")
                      .arg(d->m_stackup->nameOptional.value);
    }
    endResetModel();
}

QModelIndex LayerStackupModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_D(const LayerStackupModel);

    if (!hasIndex(row, column, parent))
        return QModelIndex();

    LayerStackupItem *parentItem;

    if (!parent.isValid())
        parentItem = d->m_root;
    else
        parentItem = static_cast<LayerStackupItem*>(parent.internalPointer());

    LayerStackupItem *childItem = parentItem->child(row);
    if (childItem != nullptr)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

QModelIndex LayerStackupModel::parent(const QModelIndex &index) const
{
    Q_D(const LayerStackupModel);

    if (!index.isValid())
        return QModelIndex();

    LayerStackupItem *childItem = static_cast<LayerStackupItem*>(index.internalPointer());
    LayerStackupItem *parentItem = childItem->parent();

    if (parentItem == d->m_root)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

int LayerStackupModel::rowCount(const QModelIndex &parent) const
{
    Q_D(const LayerStackupModel);

    LayerStackupItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = d->m_root;
    else
        parentItem = static_cast<LayerStackupItem*>(parent.internalPointer());

    return parentItem->childCount();
}

int LayerStackupModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return ColumnCount;
}

QVariant LayerStackupModel::data(const QModelIndex &index, int role) const
{
    Q_D(const LayerStackupModel);

    if (!index.isValid())
        return QVariant();

    LayerStackupItem *item = static_cast<LayerStackupItem*>(index.internalPointer());

    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch (index.column())
        {
            case NameColumn:
                return item->name();
            case ThicknessColumn:
                return item->thickness();
            case CommentColumn:
                return item->comment();
            case BomItemRefColumn:
                return item->bomItemRef();
            default:
                return QVariant();
        }
    }
    else if (role == ItemModel::SpecificationModelRole)
    {
        return item->specificationKeys();
    }
    else if (role == ItemModel::DocumentRole)
        return QVariant::fromValue<const Document *>(d->m_document);

    return QVariant();
}


QVariant LayerStackupModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal)
        return QVariant();

    if (role != Qt::DisplayRole)
        return QVariant();

    switch (section)
    {
        case NameColumn:
            return "Name";
        case ThicknessColumn:
            return "Thickness";
        case CommentColumn:
            return "Comment";
        case BomItemRefColumn:
            return "BOM Item";
        default:
            return QVariant();
    }
}
